/**
 * Baijiahulian.com Inc. Copyright (c) 2014-2016 All Rights Reserved.
 */
package com.baijia.web.controller.v1;

import static com.baijia.web.constant.Const.CONST_PASSPORT_FAILED_DEPTH;

import com.baijia.aegis.service.BlockService;
import com.baijia.authentication.AccountType;
import com.baijia.authentication.Authentication;
import com.baijia.authentication.AuthenticationMetaDataPopulator;
import com.baijia.authentication.ThirdLogin;
import com.baijia.authentication.handler.exception.AuthenticationException;
import com.baijia.authentication.handler.exception.BadCredentialsAuthenticationException;
import com.baijia.authentication.handler.exception.UrlMessageException;
import com.baijia.authentication.principal.Credentials;
import com.baijia.authentication.principal.SimpleWebApplicationServiceImpl;
import com.baijia.authentication.principal.credentials.JigouCascadeAccountCredentials;
import com.baijia.authentication.principal.credentials.JigouCredentials;
import com.baijia.authentication.principal.credentials.PassportCredentials;
import com.baijia.common.enums.ResponseStatus;
import com.baijia.common.enums.UserRoleType;
import com.baijia.dao.PassportDao;
import com.baijia.dao.UserLoginLogDao;
import com.baijia.dao.po.AssertionVO;
import com.baijia.dao.po.Passport;
import com.baijia.dao.po.UserLoginLog;
import com.baijia.dao.po.UserLoginLog.AuthType;
import com.baijia.exception.ErrorCode;
import com.baijia.services.UnauthorizedServiceException;
import com.baijia.ticket.TicketException;
import com.baijia.ticket.TicketGrantingTicket;
import com.baijia.util.IpUtil;
import com.baijia.util.JigouPasswordUtil;
import com.baijia.util.MobileLoginUtil;
import com.baijia.util.RedisUtil;
import com.baijia.util.RestUtils;
import com.baijia.util.SerializeUtil;
import com.baijia.validation.Assertion;
import com.baijia.web.constant.Const;
import com.baijia.web.controller.AbstractAuthorizeController;
import com.baijia.web.dto.response.MobileResponse;
import com.baijia.web.service.IPassportValidateService;
import com.baijia.web.service.IThirdLoginService;
import com.baijia.web.util.URLUtil;
import com.baijia.web.vo.BindPassportVO;
import com.baijia.web.vo.PassportRequestVO;
import com.baijia.web.vo.RestLoginVo;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Controller
@RequestMapping("/v1/rest")
public class RestAuthorizeController extends AbstractAuthorizeController {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    // private static final String TEST_SIGN = "test123";

    @Value("${rest.appId}")
    private String appId;
    @Value("${rest.appKey}")
    private String appKey;
    @Value("${rest.appServiceUrl}")
    private String baseUrl;
    @Value("${passport.server.name}")
    private String baseDomain;

    @Resource
    private PassportDao passportDao;

    @Resource
    private IPassportValidateService passportValidateService;

    @Resource
    private IThirdLoginService thirdLoginService;

    @Resource
    private UserLoginLogDao userLoginLogDao;

    @Resource
    private BlockService blockService;

    @PostConstruct
    protected void init() {
        RestUtils.init(appId, appKey, baseUrl);
    }

    private final Integer MAX_TIMES = 3;

    @RequestMapping(value = "login.do")
    @ResponseBody
    public MobileResponse<RestLoginVo> login(HttpServletRequest request, HttpServletResponse httpServletResponse) {
        // WARNING: 不要再犯把密码明文打到日志里的错误啊！！已经出现过几次case了！
        Map<String, String[]> paramsWithoutPassword = new HashMap<>(request.getParameterMap());
        paramsWithoutPassword.put(Credentials.PASSWORD, new String[] {});
        logger.info("v1.rest.login.do : request params={}", paramsWithoutPassword);

        MobileResponse<RestLoginVo> response = new MobileResponse<>();
        Credentials credentials = createCredentials(request, response, false); // isSms = false
        if (credentials == null) {
            logger.warn("v1.rest.login.do : createCredentials failed");
            return response;
        }

        if (needCaptcha(request)) { // FIXME 登录方控制是否做验证码校验不太好
            String key = request.getParameter(Credentials.USER_NAME) + Const.LOGIN_COUNT_SUFFIX;
            String failTimesStr = RedisUtil.hGet(key, Const.LOGIN_FAIL_TIMES);
            logger.info("v1.rest.login.do : failTimesStr={}", failTimesStr);

            if (StringUtils.isNotBlank(failTimesStr) && Long.valueOf(failTimesStr) > Const.MAX_RETRIES) {// 需要验证图形验证码
                if (!passportValidateService.validateCaptcha(request, response)) {
                    return response;
                }
            }

            if (!handleLogin(request, response, httpServletResponse, credentials)) {
                recordLoginFailTimes(key, response);
                return response;
            }

            RedisUtil.del(key.getBytes());
        } else {
            if (!handleLogin(request, response, httpServletResponse, credentials)) {
                return response;
            }
        }

        setLoginLogCache(request);
        return response;
    }

    private void setLoginLogCache(HttpServletRequest request) {
        UserLoginLog userLoginLog = new UserLoginLog();
        String username = request.getParameter("username");
        String loginIp = request.getParameter("login_ip");
        String compaignId = request.getParameter("compaign_id");
        String statAreaId = request.getParameter("stat_area_id");
        String statIp = request.getParameter("stat_ip");
        String statClient = request.getParameter("stat_client");
        String statChannel = request.getParameter("stat_channel");
        String statDevice = request.getParameter("stat_device");
        String version = request.getParameter("version");
        String statMac = request.getParameter("stat_mac");
        String statImei = request.getParameter("stat_imei");

        userLoginLog.setLoginIp(loginIp);
        userLoginLog.setCompaignId(compaignId);
        if (StringUtils.isNotBlank(statAreaId)) {
            userLoginLog.setStatAreaId(Integer.valueOf(statAreaId));
        }

        userLoginLog.setStatIp(statIp);
        if (StringUtils.isNotBlank(statClient)) {
            userLoginLog.setStatClient(Integer.valueOf(statClient));
        }

        userLoginLog.setStatChannel(statChannel);
        userLoginLog.setStatDevice(statDevice);
        userLoginLog.setVersion(version);
        userLoginLog.setStatMac(statMac);
        userLoginLog.setStatImei(statImei);
        userLoginLog.setAuthType(AuthType.PASSWORD.getCode());

        RedisUtil.setex((username + Const.LOGIN_LOG_SUFFIX).getBytes(), Const.LOG_EXPIRE,
            SerializeUtil.serialize(userLoginLog));
    }

    @RequestMapping("/validateServiceTicket.do")
    @ResponseBody
    public MobileResponse<AssertionVO> validateServiceTicket(HttpServletRequest request) throws NumberFormatException,
        IOException {

        MobileResponse<AssertionVO> response = new MobileResponse<>();

        SimpleWebApplicationServiceImpl service = SimpleWebApplicationServiceImpl.createServiceFrom(request);
        String serviceTicketId = service != null ? service.getArtifactId() : null;
        if (service == null || serviceTicketId == null) {
            logger.info(String.format("Could not process request; Service: %s, Service Ticket Id: %s", service,
                serviceTicketId));
            logger.debug(ResponseStatus.NO_TICKET.getMsg());
            response.fail().msg(ResponseStatus.NO_TICKET.getMsg());
            return response;
        }

        String appId = request.getParameter(APPID);
        if (StringUtils.isBlank(appId)) {
            response.fail().msg(ResponseStatus.PARAM_NULL_APPID.getMsg());
            return response;
        }

        try {
            Assertion assertion = this.centralAuthenticationService.validateServiceTicket(serviceTicketId, service);
            AssertionVO assertionVO = new AssertionVO();
            if (assertion != null && assertion.getChainedAuthentications() != null
                && assertion.getChainedAuthentications().size() > 0) {
                Authentication authentication = assertion.getChainedAuthentications().get(0);

                AccountType accountType = AccountType.getAccountTypeByCode(Integer.valueOf(appId));
                switch (accountType) {
                    case USER:
                    case ORG_TEACHER:
                        String mobile =
                            (String) authentication.getAttributes().get(AuthenticationMetaDataPopulator.ACCOUNT_MOBILE);
                        Long userId =
                            (Long) authentication.getAttributes().get(AuthenticationMetaDataPopulator.ACCOUNT_ID);
                        assertionVO.setMobile(mobile);
                        assertionVO.setUser_id(userId);
                        break;
                    case PASSPORT:
                    default:
                        Long passportId =
                            (Long) authentication.getAttributes().get(AuthenticationMetaDataPopulator.PASSPORT_ID);
                        if (passportId != null) {
                            assertionVO.setPassport_id(passportId);
                        }

                        Passport passport = passportDao.getById(passportId);
                        if (passport != null) {
                            assertionVO.setMobile(passport.getMobile());
                            assertionVO.setUser_id(passport.getUserId());
                            assertionVO.setOrg_id(passport.getOrgId());
                        }
                }

                if (StringUtils.isNotBlank(RedisUtil.hGet(TicketGrantingTicket.TGTID, serviceTicketId))) {
                    String tgtId = RedisUtil.hGet(TicketGrantingTicket.TGTID, serviceTicketId);
                    try {
                        assertionVO.setUser_role(Integer.valueOf(RedisUtil.hGet(Credentials.USER_ROLE, tgtId)));
                    } catch (NumberFormatException e) {
                        logger.error("get userRole error!tgtId:" + tgtId, e);
                        response.fail().msg("get userRole error!");
                        return response;
                    }
                }
            }

            response.data(assertionVO);
            // if (RedisUtil.get((assertionVO.getMobile() + Const.LOGIN_LOG_SUFFIX).getBytes()) != null) { FIXME useless
            // code
            UserLoginLog userLoginLog =
                (UserLoginLog) SerializeUtil
                    .unserialize(RedisUtil.get((assertionVO.getMobile() + Const.LOGIN_LOG_SUFFIX).getBytes()));
            if (userLoginLog == null) {// 用户通过cookie登录
                userLoginLog = new UserLoginLog();
                userLoginLog.setAuthType(AuthType.COOKIE.getCode());
            } else {
                RedisUtil.del((assertionVO.getMobile() + Const.LOGIN_LOG_SUFFIX).getBytes());
            }

            userLoginLog.setUserId(assertionVO.getUser_id());
            userLoginLog.setUserRole(assertionVO.getUser_role());
            logger.info("user login info: {}", userLoginLog);

            userLoginLogDao.save(userLoginLog);
            // }
        } catch (AuthenticationException e) {
            logger.error("validateServiceTicket biz error!service:" + service, e);
            response.fail().msg((ResponseStatus.AUTH_FAIL.getMsg()));
        } catch (Exception e) {
            logger.error("validateServiceTicket sys error!service:" + service, e);
            response.fail().msg((ResponseStatus.SYSTEM_ERROR.getMsg()));
        }

        return response;
    }

    /**
     * login by sms code, if there is no user in db, create a user, then send init password.
     * 
     * @param request
     * @return
     */
    @RequestMapping("/loginSms.do")
    @ResponseBody
    public MobileResponse<RestLoginVo> loginBySmsCode(HttpServletRequest request, PassportRequestVO passportRequestVO,
        HttpServletResponse httpServletResponse) {
        logger.info("loginSms.do params: {}", request.getParameterMap());
        MobileResponse<RestLoginVo> response = new MobileResponse<>();
        String mobile = request.getParameter(Credentials.USER_NAME);
        if (!passportValidateService.validateLoginParams(passportRequestVO, response)) {
            return response;
        }

        /** if create = 1, passport is not exist, then create a new account. */
        // String create = request.getParameter("create");
        if (!passportValidateService.validateMobileLoginSms(mobile, request, response)) {
            return response;
        }
        Credentials credentials = createCredentials(request, response, true);
        if (credentials == null) {
            return response;
        }
        setLoginLogCache(request);
        handleLogin(request, response, httpServletResponse, credentials);
        return response;

    }

    @RequestMapping("/gainSt.do")
    @ResponseBody
    public MobileResponse<RestLoginVo> gainSt(HttpServletRequest request) throws IOException {
        MobileResponse<RestLoginVo> response = new MobileResponse<>();
        String tgtId = request.getParameter("tgt");
        String st;
        SimpleWebApplicationServiceImpl targetService;
        targetService = SimpleWebApplicationServiceImpl.createServiceFrom(request, httpClient);
        logger.info("targetService:" + targetService);
        if (!targetService.validDomain(servicesManager.findServiceBy(targetService))) {
            logger.error("valid domain error!original url :{},domains:{}", targetService.getOriginalUrl(),
                servicesManager.findServiceBy(targetService).getAllowDomains() + "|targetService:" + targetService);
            response.fail().msg("access denied!");
            return response;
        }
        if (!StringUtils.isBlank(tgtId)) {
            try {
                st = centralAuthenticationService.grantServiceTicket(tgtId, targetService, null);
                RestLoginVo restLoginVo = new RestLoginVo();
                restLoginVo.setSt(st);
                restLoginVo.setTgt(tgtId);
                response.data(restLoginVo);
            } catch (AuthenticationException e) {
                e.printStackTrace();
                logger.info("createTicketGrantingTicket error!targetService:" + targetService, e);
                response.fail().msg("Login fail! Msg: " + e.getMessage());
            } catch (TicketException e) {
                logger.error("Ticket exception!", e);
                response.fail().msg("Login fail! Msg: " + e.getMessage());
            }
        }

        return response;
    }

    @RequestMapping({ "validateCaptcha.do", "validateCaptcha.json" })
    @ResponseBody
    public MobileResponse<String> validateCaptcha(HttpServletRequest request) {
        MobileResponse<String> response = new MobileResponse<>();
        if (!passportValidateService.validateCaptcha(request, response)) {
            return response;
        }
        return response.msg("success");
    }

    @RequestMapping({ "sendPasscode.do", "sendPasscode.json" })
    @ResponseBody
    public MobileResponse<String> sendPasscode(String mobile,
        @RequestParam(value = "client_ip", required = false) String client_ip,
        @RequestParam(required = false) Integer is_voice, HttpServletRequest request) {

        MobileResponse<String> response = new MobileResponse<>();
        if (StringUtils.isEmpty(client_ip)) {
            client_ip = IpUtil.getIpAddr(request);
        }

        if (needCaptcha(request)) { // 发送次数过多时需要图形验证码校验
            // NOTE 由于用户投诉频繁接到被刷的短信验证码，临时改为任何情况下都要做图形校验
            if (!passportValidateService.validateCaptcha(request, response)) {
                return response;
            }

            // [不要删除] 根据IP判断，大于3次获取短信验证码，则需要图形验证
            // String countStr = RedisUtil.hGet(client_ip, Const.LOGIN_CLIENT_IP);
            // if (StringUtils.isNotBlank(countStr) && Integer.valueOf(countStr) > MAX_TIMES) {
            // logger.warn("client_ip:{} send message has more than {} times, captcha code has needed!mobile:{},",
            // client_ip, MAX_TIMES, mobile);
            //
            // if (!passportValidateService.validateCaptcha(request, response)) {
            // return response;
            // }
            // } else {
            // int count = 0;
            // if (StringUtils.isNotBlank(countStr)) {
            // count = Integer.valueOf(countStr);
            // }
            // int timeRemain = getTimeRemain();
            // RedisUtil.hSet(client_ip, Const.LOGIN_CLIENT_IP, String.valueOf(count + 1), timeRemain);
            // }
        }

        // 使用严格模式，即当前用户如果被禁止登录了，第一次尝试都把禁止登录时间延长一倍
        if (MobileLoginUtil.isUserBlock(mobile)) {
            logger.warn("用户当前被禁止登录，请1小时后再试！mobile:{}, client_ip:{}", mobile, client_ip);
            response.fail().msg("用户当前被禁止登录，请1小时后再试！");
            MobileLoginUtil.blockUser(mobile, Const.BLOCK_IN_SECONDS);
        } else if (!MobileLoginUtil.canSendPasscode(mobile)) {
            logger.warn("获取验证码过于频繁，请1分钟后再试！mobile:{}, client_ip:{}", mobile, client_ip);
            response.fail().msg("获取验证码过于频繁，请1分钟后再试！");
            MobileLoginUtil.touchSendSeperate(mobile);
        } else if (MobileLoginUtil.getUserRetries(mobile) > Const.MAX_RETRIES) {
            logger.warn("超过最大重试次数，用户将被禁止登录1小时！mobile:{}, client_ip:{}", mobile, client_ip);
            response.fail().msg("超过最大重试次数，用户将被禁止登录1小时！");
            MobileLoginUtil.blockUser(mobile, Const.BLOCK_IN_SECONDS);
        } else {
            Map<String, String> result = MobileLoginUtil.sendPasscode(mobile, is_voice);
            if (result != null) {
                response.setCode(Integer.valueOf(result.get("result_code")));
                response.setMsg(result.get("result_msg"));
            } else {
                response.fail().msg("验证码发送失败，请稍后重试！");
            }
        }

        return response;
    }

    private int getTimeRemain() {
        Calendar cal = Calendar.getInstance();
        Date date = new Date();
        cal.setTime(date);
        cal.set(Calendar.HOUR_OF_DAY, 0);
        cal.set(Calendar.MINUTE, 0);
        cal.set(Calendar.SECOND, 0);
        cal.set(Calendar.MILLISECOND, 0);
        cal.add(Calendar.DAY_OF_MONTH, 1);
        return (int) (cal.getTime().getTime() - date.getTime()) / 1000;
    }

    @RequestMapping({ "sendPasscodeTest.do", "sendPasscodeTest.json" })
    @ResponseBody
    public MobileResponse<Map<String, String>> sendPasscodeTest(HttpServletRequest request, String mobile,
        @RequestParam(required = false) Integer is_voice) {
        logger.info("sendPasscodeTest params: {}", request.getParameterMap());
        MobileResponse<Map<String, String>> response = new MobileResponse<>();
        Map<String, String> result = MobileLoginUtil.sendPasscodeTest(mobile, 0);
        response.setData(result);
        return response;
    }

    @RequestMapping({ "clearRedis.do", "clearRedis.json" })
    @ResponseBody
    public MobileResponse<Map<String, String>> clearRedis(String key, String field) {
        MobileResponse<Map<String, String>> response = new MobileResponse<>();
        String countStr = RedisUtil.hGet(key, field);
        if (StringUtils.isNotBlank(countStr)) {
            RedisUtil.hDel(key, field);
            response.setMsg("清理key:" + key + " field: " + field + " 成功!");
        } else {
            response.setMsg("key:" + key + " field: " + field + " is not exist!");
        }

        return response;
    }

    @RequestMapping("logout.do")
    @ResponseBody
    public MobileResponse<String> logout(HttpServletRequest request, HttpServletResponse response) {
        String ticketGrantingTicketId = cookieRetrievingCookieGenerator.retrieveCookieValue(request);
        logger.info("logout TGT:{}", ticketGrantingTicketId);
        handleLogout(ticketGrantingTicketId, request, response);
        return new MobileResponse<String>().msg("退出成功！");
    }

    @RequestMapping(value = "/reg.do", method = RequestMethod.POST)
    @ResponseBody
    public MobileResponse register(HttpServletRequest request, HttpServletResponse httpServletResponse)
        throws IOException {
        MobileResponse<RestLoginVo> response = new MobileResponse<>();
        Map paramsWithoutPwd = new HashMap<>(request.getParameterMap());
        paramsWithoutPwd.put(Credentials.PASSWORD, "******");
        logger.info("user reg request parameters: {}", paramsWithoutPwd);

        String mobile = request.getParameter(Credentials.USER_NAME);
        String password = request.getParameter(Credentials.PASSWORD);
        String code = request.getParameter(Credentials.CODE);
        String email = request.getParameter("email");
        if (!passportValidateService.validateSmsCode(mobile, request, code, response)) {
            return response;
        }
        Passport passport = new Passport();
        passport.setMobile(mobile);
        passport.setEmail(email);
        passport.setMobileVerify(Passport.MobileVerify.YES.getId());
        String pwd = JigouPasswordUtil.createHash(password);
        passport.setPassword(pwd);
        // createAndSynPassport(passport, request, response, httpServletResponse);
        createNewAccount(passport, request, response, httpServletResponse);
        return response;

    }

    private void createNewAccount(Passport passport, HttpServletRequest request, MobileResponse<RestLoginVo> response,
        HttpServletResponse httpServletResponse) {
        String appId = request.getParameter(Credentials.APPID);
        AccountType accountType = AccountType.getAccountTypeByCode(Integer.valueOf(appId));
        switch (accountType) {
            case USER:
            case ORG_TEACHER:
                if (passportService.createUser(passport, request) == 0) {
                    logger.info("fail to create a user account, mobile:{}", passport.getUsername());
                    response.fail().msg("fail to create a user account.");
                    return;
                }
                break;
            default:
                // TODO
                break;
        }

        Credentials credentials = createCredentials(request, response, false);
        if (credentials != null) {
            handleLogin(request, response, httpServletResponse, credentials);
        }

    }

    @RequestMapping(value = "/register.json", method = RequestMethod.POST)
    @ResponseBody
    public MobileResponse reg(HttpServletRequest request, HttpServletResponse httpServletResponse,
        PassportRequestVO passportRequestVO) throws IOException {
        MobileResponse<RestLoginVo> response = new MobileResponse<>();
        MobileResponse res = new MobileResponse();
        if (!passportValidateService.validateSmsCode(passportRequestVO.getUsername(), request,
            passportRequestVO.getCode(), response)) {
            return response;
        }
        Passport passport = transToPassport(passportRequestVO);
        if (!createAndSynPassport(passport, request, response, httpServletResponse)) {
            return response;
        }
        RestLoginVo restLoginVo = response.getData();
        Map resultMap = new HashMap();
        resultMap.put(Const.REDIRECT_URL, restLoginVo.getRedirectUrl());
        res.setData(resultMap);
        return res;

    }

    @RequestMapping(value = "/login.json", method = RequestMethod.POST)
    @ResponseBody
    public MobileResponse<?> loginWeb(HttpServletRequest request, HttpServletResponse httpServletResponse)
        throws IOException {
        MobileResponse<RestLoginVo> response = new MobileResponse<>();

        Credentials credentials = createCredentials(request, response, false);
        if (credentials == null) {
            logger.debug("loginWeb : createCredentials failed");
            return response;
        }

        if (StringUtils.isNotBlank(request.getParameter(Credentials.NEED_CAPTCHA))
            && "1".equals(request.getParameter(Credentials.NEED_CAPTCHA))) {
            // 主站pc端需要 密码错误3次需要输入图形验证码
            String key = request.getParameter(Credentials.USER_NAME) + Const.LOGIN_COUNT_SUFFIX;
            String failTimesStr = RedisUtil.hGet(key, Const.LOGIN_FAIL_TIMES);

            logger.info("login.do failTimesStr : {}", failTimesStr);
            if (StringUtils.isNotBlank(failTimesStr) && Long.valueOf(failTimesStr) > Const.MAX_RETRIES) { // 需要验证图形验证码
                if (!passportValidateService.validateCaptcha(request, response)) {
                    return response;
                }
            }

            if (!handleLogin(request, response, httpServletResponse, credentials)) {
                recordLoginFailTimes(key, response);
                return response;
            }

            RedisUtil.del(key.getBytes());
        } else {
            if (!handleLogin(request, response, httpServletResponse, credentials)) {
                return response;
            }
        }

        RestLoginVo restLoginVo = response.getData();
        Map<String, String> resultMap = new HashMap<>();
        resultMap.put(Const.REDIRECT_URL, restLoginVo.getRedirectUrl());

        MobileResponse<Map<String, String>> res = new MobileResponse<>();
        res.setData(resultMap);

        return res;
    }

    @RequestMapping(value = "/authorize-callback/{channel}.do")
    public String callback(HttpServletRequest request, HttpServletResponse httpServletResponse,
        @PathVariable("channel") String channel) throws IOException {
        MobileResponse<RestLoginVo> response = new MobileResponse();
        String code;
        if (ThirdLogin.ALIPAY.equals(channel)) {
            code = request.getParameter("auth_code");
        } else
            code = request.getParameter(Credentials.CODE);
        if (StringUtils.isBlank(code)) {
            response.fail().msg(ErrorCode.THIRD_LOGIN_ERROR.getMessage());
            return "redirect:" + baseDomain + "/view/pc/loginFailure.html";
        }
        Credentials credentials;
        String username = null;
        switch (channel) {
            case ThirdLogin.WEIBO:
                username = thirdLoginService.login(ThirdLogin.WEIBO, code, response);
                break;
            case ThirdLogin.QQ:
                username = thirdLoginService.login(ThirdLogin.QQ, code, response);
                break;
            case ThirdLogin.ALIPAY:
                username = thirdLoginService.login(ThirdLogin.ALIPAY, code, response);
                break;
            case ThirdLogin.RENREN:
                username = thirdLoginService.login(ThirdLogin.RENREN, code, response);
                break;
            case ThirdLogin.WECHAT:
                username = thirdLoginService.login(ThirdLogin.WECHAT, code, response);
                break;
            default:
                response.fail().msg(ErrorCode.THIRD_LOGIN_ERROR.getMessage());
                break;
        }
        if (username == null) {
            if (ErrorCode.NEED_BIND_ACCOUNT.getCode() == response.getCode()) {
                return "redirect:" + baseDomain + "/view/pc/bindAccount.html?third_id=" + response.getData();
            }
            logger.warn("third login occurs error: {}", response.getMsg());
            return "redirect:" + baseDomain + "/view/pc/loginFailure.html";
        } else {
            credentials = createDefaultCredentials(request, username);
        }

        if (!handleLogin(request, response, httpServletResponse, credentials)) {
            logger.warn("third login occurs error: {}", response.getMsg());
            return "redirect:" + baseDomain + "/view/pc/loginFailure.html";
        }
        RestLoginVo restLoginVo = response.getData();
        return "redirect:" + restLoginVo.getRedirectUrl();
    }

    private Credentials createDefaultCredentials(HttpServletRequest request, String username) {
        String userRole = request.getParameter(Credentials.USER_ROLE);
        if (StringUtils.isBlank(userRole)) {
            userRole = String.valueOf(UserRoleType.STUDENT.getId()); // FIXME 缺省使用User用户登录
        }

        // 生成credentials
        Credentials credentials =
            new PassportCredentials(username, null, userRole, request.getParameter(Credentials.COUNTRY_CODE), true,
                false, Integer.valueOf(request.getParameter(Credentials.APPID)));
        return credentials;
    }

    @RequestMapping(value = "/bindPassport.json")
    @ResponseBody
    public MobileResponse bindPassport(HttpServletRequest request, BindPassportVO bindPassportVO,
        HttpServletResponse httpServletResponse) {
        MobileResponse<RestLoginVo> response = new MobileResponse();
        String mobile = bindPassportVO.getMobile();
        if (!passportValidateService.validateBindPassportVo(bindPassportVO, response, request)) {
            return response;
        }
        Credentials credentials =
            new PassportCredentials(mobile, "", String.valueOf(UserRoleType.STUDENT.getId()), "", true, false,
                Integer.valueOf(request.getParameter(Credentials.APPID)));
        if (credentials == null) {
            return response;
        }
        if (!handleLogin(request, response, httpServletResponse, credentials)) {
            return response;
        }

        MobileResponse res = new MobileResponse();
        RestLoginVo restLoginVo = response.getData();
        Map resultMap = new HashMap();
        resultMap.put(Const.REDIRECT_URL, restLoginVo.getRedirectUrl());
        res.setData(resultMap);

        return res;
    }

    @RequestMapping(value = "/loginSms.json", method = RequestMethod.POST)
    @ResponseBody
    public MobileResponse<?> loginSmsWeb(HttpServletRequest request, HttpServletResponse httpServletResponse,
        PassportRequestVO passportRequestVO) throws IOException {
        MobileResponse<RestLoginVo> response = new MobileResponse<>();

        if (!passportValidateService.validateLoginParams(passportRequestVO, response)) {
            return response;
        }
        if (!passportValidateService.validateMobileLoginSms(passportRequestVO.getUsername(), request, response)) {
            return response;
        }
        Credentials credentials = createCredentials(request, response, true);
        if (credentials == null) {
            return response;
        }
        if (!handleLogin(request, response, httpServletResponse, credentials)) {
            return response;
        }

        RestLoginVo restLoginVo = response.getData();
        Map<String, String> resultMap = new HashMap<>();
        resultMap.put(Const.REDIRECT_URL, restLoginVo.getRedirectUrl());

        MobileResponse<Map<String, String>> res = new MobileResponse<>();
        res.setData(resultMap);

        return res;
    }

    private Credentials createCredentials(HttpServletRequest request, MobileResponse<?> response, boolean isSms) {
        Credentials credentials = generateCredentials(request, isSms);
        if (!passportService.existedUser(request.getParameter(Credentials.USER_NAME), credentials.getAccountType())) {
            response.fail().msg(ErrorCode.CREDENTIALS_ERROR.getMessage());
            return null;
        }

        return credentials;
    }

    private boolean createAndSynPassport(Passport passport, HttpServletRequest request, MobileResponse response,
        HttpServletResponse httpServletResponse) {
        try {
            long passportId = passportService.createAccount(passport);
            if (passportId > 0) {// 帐号注册成功
                passport.setId(passportId);
                /** passport帐号注册成功, 需要写主站用户表和机构用户表 */
                if (passportService.synData(passport) <= 0) {
                    response.fail().msg(ErrorCode.SYN_DATA_ERROR.getMessage());
                }
                Credentials credentials = createCredentials(request, response, false);
                if (credentials == null) {
                    return false;
                }
                if (!handleLogin(request, response, httpServletResponse, credentials)) {
                    return false;
                }
            } else {
                response.fail().msg(ErrorCode.CREATE_ACCOUNT_FAIL.getMessage());
                return false;
            }
        } catch (Exception e) {
            logger.error("create passport or synData occurs error,{}", e.getMessage());
            response.fail().msg(ErrorCode.SYN_DATA_ERROR.getMessage());
            return false;
        }

        return true;
    }

    private Passport transToPassport(PassportRequestVO passportRequestVO) {
        Passport passport = new Passport();
        passport.setMobile(passportRequestVO.getUsername());
        String password = passportRequestVO.getPassword();
        if (StringUtils.isNotBlank(password)) {
            String pwd = JigouPasswordUtil.createHash(password);
            passport.setPassword(pwd);
        }
        passport.setEmail(passportRequestVO.getEmail());
        passport.setUsername(passportRequestVO.getName());
        passport.setMobileVerify(Passport.MobileVerify.YES.getId());
        return passport;
    }

    private void recordLoginFailTimes(String key, MobileResponse<?> response) {
        String failTimesStr = RedisUtil.hGet(key, Const.LOGIN_FAIL_TIMES);
        logger.info("needCpatcha failTimesStr:{}", failTimesStr);

        if (StringUtils.isNotBlank(failTimesStr) && Long.valueOf(failTimesStr) > Const.MAX_RETRIES) {
            // response.setCode(ErrorCode.NEED_CAPTCHA.getCode());
            // response.setMsg(response.getMsg());
            logger.warn("recordLoginFailTimes : already exceed max retries");
        } else {
            RedisUtil.incrBy(key, Const.LOGIN_FAIL_TIMES, 1L);
        }
    }

    protected boolean handleLogin(HttpServletRequest request, MobileResponse<RestLoginVo> response,
        HttpServletResponse httpServletResponse, Credentials credentials) {

        SimpleWebApplicationServiceImpl service = null;
        try {
            service = SimpleWebApplicationServiceImpl.createServiceFrom(request, httpClient);
            logger.info("service : {}", service);

            if (service == null) {
                service =
                    SimpleWebApplicationServiceImpl.createDefaultService(request,
                        passportConfig.getDefaultTargetService());
            }

            String TGTID = centralAuthenticationService.createTicketGrantingTicket(credentials);
            String ST = centralAuthenticationService.grantServiceTicket(TGTID, service);

            String appId = request.getParameter(Credentials.APPID);
            if (StringUtils.isBlank(appId)) { // 兼容app_id请求参数
                appId = request.getParameter(Credentials.APP_ID);
            }
            AccountType accountType = AccountType.getAccountTypeByCode(Integer.valueOf(appId));
            switch (accountType) {
                case USER:
                case ORG_TEACHER:
                    cookieRetrievingCookieGenerator.addUserCookie(request, httpServletResponse, TGTID);
                    break;
                default:
                    cookieRetrievingCookieGenerator.addCookie(request, httpServletResponse, TGTID);
                    break;
            }

            Cookie cookie = org.springframework.web.util.WebUtils.getCookie(request, CONST_PASSPORT_FAILED_DEPTH);
            if (cookie != null) {
                cookie.setMaxAge(0);
                logger
                    .info("Clear _const_passport_failed_depth_ cookie {}", ToStringBuilder.reflectionToString(cookie));
                httpServletResponse.addCookie(cookie);
            }

            RestLoginVo restLoginVo = new RestLoginVo();
            restLoginVo.setSt(ST);
            restLoginVo.setTgt(TGTID);
            if (StringUtils.isNotBlank(RedisUtil.hGet(TicketGrantingTicket.TGT_EXPIRED_AT, ST))) {
                restLoginVo.setExpired_at(Long.valueOf(RedisUtil.hGet(TicketGrantingTicket.TGT_EXPIRED_AT, ST)));
            }
            String validUrl = URLUtil.getValidSTUrl(service, ST, passportConfig.getLoginUrl());
            restLoginVo.setRedirectUrl(validUrl);
            logger.info("restLoginVo:{}", restLoginVo);

            response.data(restLoginVo);
        } catch (UrlMessageException e) {
            logger.info("login fail! credentials:{},msg:{}", credentials.getUsername(), e.getMessage());
            response.fail().msg(e.getMessage());
            return false;
        } catch (UnauthorizedServiceException e) {
            logger.info("login fail! unauthorized service:{},msg:{}", service, e.getMessage());
            response.fail().msg(e.getMessage());
            return false;
        } catch (BadCredentialsAuthenticationException e) {
            logger.warn(BadCredentialsAuthenticationException.CODE);
            response.fail().msg("用户登录密码错误");
            return false;
        } catch (AuthenticationException e) {
            logger.warn("createTicketGrantingTicket error!credentials:" + credentials.getUsername(), e);
            response.fail().msg(ErrorCode.BUSINESS_ERROR.getMessage());
            return false;
        } catch (Exception e) {
            logger.error("System error!", e);
            response.fail().msg(ErrorCode.SYSTEM_ERROR.getMessage());
            return false;
        }

        return true;
    }

    @RequestMapping(value = "/checkLogin.json")
    @ResponseBody
    public MobileResponse<RestLoginVo> checkLogin(HttpServletRequest request, HttpServletResponse httpServletResponse,
        String uuid) {

        MobileResponse<RestLoginVo> response = new MobileResponse<>();
        if (StringUtils.isNotBlank(uuid)) {
            if (RedisUtil.hGet(uuid, Const.UUID) != null) {
                if ("true".equals(RedisUtil.hGet(uuid, Const.SCAN_QRCODE_SUCCESS))) {// app已经扫描
                    if (RedisUtil.hGet(uuid, Const.USERNAME) != null) {// app已经扫描并登录
                        String username = RedisUtil.hGet(uuid, Const.USERNAME);
                        String appId = RedisUtil.hGet(uuid, Const.APPID);
                        if (AccountType.ORG.getCode() == Integer.valueOf(appId).intValue()) {
                            Credentials credentials = new JigouCredentials(username, "", "", true);
                            if (!handleLogin(request, response, httpServletResponse, credentials)) {
                                return response;
                            }
                        } else if (AccountType.ORG_SUB_ACCT.getCode() == Integer.valueOf(appId).intValue()) {
                            Credentials credentials = new JigouCascadeAccountCredentials(username, "", true);
                            if (!handleLogin(request, response, httpServletResponse, credentials)) {
                                return response;
                            }
                        }
                    } else { // app已经扫描但未登录
                        response.setCode(ErrorCode.QRCODE_SCAN_NOT_LOGIN_ERROR.getCode());
                        response.msg(ErrorCode.QRCODE_SCAN_NOT_LOGIN_ERROR.getMessage());
                    }
                } else { // app还未扫描
                    response.setCode(ErrorCode.QRCODE_NOT_SCAN_ERROR.getCode());
                    response.msg(ErrorCode.QRCODE_NOT_SCAN_ERROR.getMessage());
                }

            } else {// 二维码已经过期
                response.setCode(ErrorCode.QRCODE_EXPIRED_ERROR.getCode());
                response.msg(ErrorCode.QRCODE_EXPIRED_ERROR.getMessage());
            }
        }

        return response;
    }

    private static final String NEED_CAPTCHA = "1";

    private boolean needCaptcha(HttpServletRequest request) {
        // 主站pc端需要 密码错误3次需要输入图形验证码
        return StringUtils.equals(request.getParameter(Credentials.NEED_CAPTCHA), NEED_CAPTCHA);
    }

    @RequestMapping({ "/validateSms.json", "/validateSms.do" })
    @ResponseBody
    public MobileResponse<String> validatePasscode(@RequestParam(value = "mobile") String mobile,
        @RequestParam(value = "code") String code) {
        MobileResponse<String> response = new MobileResponse<>();
        if (MobileLoginUtil.validPasscode(mobile, code)) {
            return response;
        } else {
            response.fail().msg("验证码不一致！");
            return response;
        }
    }
}
